package com.google.code.mircle.core.transfers;

import java.io.File;
import java.io.IOException;
import java.net.ConnectException;
import java.net.URL;

import com.github.axet.wget.SpeedInfo;
import com.github.axet.wget.WGet;
import com.github.axet.wget.info.DownloadError;
import com.github.axet.wget.info.DownloadInfo;
import com.github.axet.wget.info.DownloadRetry;
import com.google.code.mircle.core.Core;
import com.google.code.mircle.core.Worker;
import com.thoughtworks.xstream.annotations.XStreamAlias;

/**
 * DirectDownload
 * 
 * Features:
 * 
 * 1) multi thread downloader
 * 
 * 2) taking name from steam (http respond)
 * 
 * 3) create missing folders
 * 
 * 4) rolling name
 * 
 * 5) retrying, continue download
 * 
 * 6) Marking contenet with a .mircle file describing source page, hash, source
 * url. Updatable to Magnet information, additional links, tags, images (xml
 * encoded and external)
 * 
 * @author axet
 * 
 */
public class DirectDownload extends Worker {

    @XStreamAlias("DirectDownloadState")
    static class State extends Worker.State {
        DownloadInfo info;
        File target;
        File targetFile;
        Exception ee = null;
        String web;
    }

    State state = new State();
    int delay = 0;

    SpeedInfo speedInfo = new SpeedInfo();

    final static int TIMEOUT = 3000;

    public DirectDownload(URL source, File target) {
        this.state.info = new DownloadInfo(source);
        this.state.info.extract();

        this.state.target = target;
    }

    public DirectDownload(URL source, File target, String web) {
        this.state.info = new DownloadInfo(source);
        this.state.info.extract();

        this.state.target = target;
        this.state.web = web.trim();
    }

    protected DirectDownload() {
    }

    @Override
    protected Worker.State getState() {
        return state;
    }

    @Override
    public void run() {
        synchronized (state) {
            if (state.downloadState == DownloadState.DOWNLOAD_COMPLETE && getTarget().exists()) {
                return;
            }
        }

        try {
            while (true) {
                synchronized (state) {
                    state.ee = null;

                    if (state.targetFile != null) {
                        if (state.info.multipart())
                            ; // multi part? overwrite;
                        else if (state.info.range()) {
                            // range download? try to resume download from last
                            // position
                            if (state.targetFile.exists() && state.targetFile.length() != state.info.getCount())
                                state.targetFile = null;
                        } else {
                            ; // single download? overwrite file
                        }
                    }

                    if (state.targetFile == null)
                        state.targetFile = WGet.calcName(state.info, state.target);
                }

                Core.invoke(new Runnable() {
                    @Override
                    public void run() {
                        synchronized (state) {
                            speedInfo.start(state.info.getCount());
                        }
                    }
                });

                WGet w = new WGet(state.info, state.targetFile, stop, new Runnable() {
                    @Override
                    public void run() {
                        Core.invoke(new Runnable() {
                            @Override
                            public void run() {
                                synchronized (state) {
                                    speedInfo.step(state.info.getCount());
                                }
                            }
                        });

                        changed();
                    }
                });

                try {
                    w.download();

                    if (stop.get())
                        return;

                    synchronized (state) {
                        state.downloadState = DownloadState.DOWNLOAD_COMPLETE;
                    }

                    return;
                } catch (DownloadRetry e) {
                    synchronized (state) {
                        state.ee = e;
                    }
                    retry();
                } catch (DownloadError e) {
                    synchronized (state) {
                        state.ee = e;
                    }
                    return;
                }
            }
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    void retry() {
        for (int i = 10; i >= 0; i--) {
            if (stop.get())
                return;

            changed();

            try {
                Thread.sleep(1000);
            } catch (Exception r) {
                throw new RuntimeException(r);
            }

            synchronized (state) {
                delay = i;
            }
        }
    }

    @Override
    public Status status() {
        synchronized (state) {
            return new Status(state.info.getLength(), state.info.getCount(), state.info.getSource().toString(), delay,
                    speedInfo.getCurrentSpeed(), speedInfo.getAverageSpeed());
        }
    }

    public File getTarget() {
        synchronized (state) {
            return state.targetFile;
        }
    }

    @Override
    public Worker.State save() {
        return state;
    }

    @Override
    public void load(Worker.State s) {
        state = (State) s;
    }

    @Override
    public String getSource() {
        synchronized (state) {
            return state.info.getSource().toString();
        }
    }

}
